/******************************************************************************* * Copyright (c) 2004, 2013 Richard Hoefter and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Richard Hoefter (richard.hoefter@web.de) - initial API and implementation, bug 95298, bug 192726, bug 201180 * IBM Corporation - nlsing and incorporating into Eclipse, bug 108276 *******************************************************************************/ package org.eclipse.ant.internal.ui.datatransfer; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.ant.internal.ui.AntUIPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.JavaRuntime; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Class to inspect classpath of an Eclipse project. */ public class EclipseClasspath { protected List<String> srcDirs = new ArrayList<>(); protected List<String> classDirs = new ArrayList<>(); protected List<List<String>> inclusionLists = new ArrayList<>(); protected List<List<String>> exclusionLists = new ArrayList<>(); protected Map<String, String> variable2valueMap = new LinkedHashMap<>(); protected List<String> rawClassPathEntries = new ArrayList<>(); protected List<String> rawClassPathEntriesAbsolute = new ArrayList<>(); private IJavaProject project; private static Map<String, IClasspathContainer> userLibraryCache = new HashMap<>(); /** * Initialize object with classpath of given project. */ public EclipseClasspath(IJavaProject project) throws JavaModelException { this.project = project; handle(project.getRawClasspath()); } /** * Initialize object with runtime classpath of given launch configuration. * * @param project * project that contains given launch configuration conf * @param conf * launch configuration * @param bootstrap * if true only bootstrap entries are added, if false only non-bootstrap entries are added */ public EclipseClasspath(IJavaProject project, ILaunchConfiguration conf, boolean bootstrap) throws CoreException { this.project = project; // convert IRuntimeClasspathEntry to IClasspathEntry IRuntimeClasspathEntry[] runtimeEntries; // see AbstractJavaLaunchConfigurationDelegate runtimeEntries = JavaRuntime.computeUnresolvedRuntimeClasspath(conf); List<IClasspathEntry> classpathEntries = new ArrayList<>(runtimeEntries.length); for (int i = 0; i < runtimeEntries.length; i++) { IRuntimeClasspathEntry entry = runtimeEntries[i]; if (bootstrap && (entry.getClasspathProperty() == IRuntimeClasspathEntry.BOOTSTRAP_CLASSES) || !bootstrap && (entry.getClasspathProperty() != IRuntimeClasspathEntry.BOOTSTRAP_CLASSES)) { // NOTE: See AbstractJavaLaunchConfigurationDelegate.getBootpathExt() // for an alternate bootclasspath detection if (entry.getClass().getName().equals("org.eclipse.jdt.internal.launching.VariableClasspathEntry")) //$NON-NLS-1$ { IClasspathEntry e = convertVariableClasspathEntry(entry); if (e != null) { classpathEntries.add(e); } } else if (entry.getClass().getName().equals("org.eclipse.jdt.internal.launching.DefaultProjectClasspathEntry")) //$NON-NLS-1$ { IClasspathEntry e = JavaCore.newProjectEntry(entry.getPath()); classpathEntries.add(e); } else if (entry.getClasspathEntry() != null) { classpathEntries.add(entry.getClasspathEntry()); } } else if (bootstrap && entry.toString().startsWith(JavaRuntime.JRE_CONTAINER)) { classpathEntries.add(entry.getClasspathEntry()); } else if (bootstrap && entry.toString().startsWith(JavaCore.USER_LIBRARY_CONTAINER_ID)) { classpathEntries.add(entry.getClasspathEntry()); } } IClasspathEntry[] entries = classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]); handle(entries); } private void handle(IClasspathEntry[] entries) throws JavaModelException { for (int i = 0; i < entries.length; i++) { handleSources(entries[i]); handleVariables(entries[i]); handleJars(entries[i]); handleLibraries(entries[i]); handleProjects(entries[i]); } } private void handleSources(IClasspathEntry entry) throws JavaModelException { String projectRoot = ExportUtil.getProjectRoot(project); String defaultClassDir = project.getOutputLocation().toString(); String defaultClassDirAbsolute = ExportUtil.resolve(project.getOutputLocation()); if (entry.getContentKind() == IPackageFragmentRoot.K_SOURCE && entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { // found source path IPath srcDirPath = entry.getPath(); IPath classDirPath = entry.getOutputLocation(); String srcDir = handleLinkedResource(srcDirPath); ExportUtil.removeProjectRoot((srcDirPath != null) ? srcDirPath.toString() : projectRoot, project.getProject()); String classDir = ExportUtil.removeProjectRoot((classDirPath != null) ? classDirPath.toString() : defaultClassDir, project.getProject()); srcDirs.add(srcDir); classDirs.add(classDir); String classDirAbsolute = (classDirPath != null) ? ExportUtil.resolve(classDirPath) : defaultClassDirAbsolute; rawClassPathEntries.add(classDir); rawClassPathEntriesAbsolute.add(classDirAbsolute); IPath[] inclusions = entry.getInclusionPatterns(); List<String> inclusionList = new ArrayList<>(); for (int j = 0; j < inclusions.length; j++) { if (inclusions[j] != null) { inclusionList.add(ExportUtil.removeProjectRoot(inclusions[j].toString(), project.getProject())); } } inclusionLists.add(inclusionList); IPath[] exclusions = entry.getExclusionPatterns(); List<String> exclusionList = new ArrayList<>(); for (int j = 0; j < exclusions.length; j++) { if (exclusions[j] != null) { exclusionList.add(ExportUtil.removeProjectRoot(exclusions[j].toString(), project.getProject())); } } exclusionLists.add(exclusionList); } } /** * Check if given source path is a linked resource. Add values to {@link #variable2valueMap} accordingly. * * @param srcDirPath * source dir as IPath * @return source directory with reference, e.g. ${MYPATH}/src, if it is no link, orginal source dir is returned */ private String handleLinkedResource(IPath srcDirPath) { String projectRoot = ExportUtil.getProjectRoot(project); String srcDir = ExportUtil.removeProjectRoot((srcDirPath != null) ? srcDirPath.toString() : projectRoot, project.getProject()); if (srcDirPath == null) { return srcDir; } IFile file; try { file = ResourcesPlugin.getWorkspace().getRoot().getFile(srcDirPath); } catch (IllegalArgumentException e) { return srcDir; } if (file.isLinked()) { String pathVariable = file.getRawLocation().segment(0).toString(); URI pathVariableValue = file.getWorkspace().getPathVariableManager().getURIValue(pathVariable); if (pathVariableValue != null) { // path variable was used String pathVariableExtension = file.getRawLocation().removeFirstSegments(1).toString(); // Bug 192726 String relativePath = ExportUtil.getRelativePath(pathVariableValue.toString(), projectRoot); variable2valueMap.put(pathVariable + ".pathvariable", relativePath); //$NON-NLS-1$ variable2valueMap.put(srcDir + ".link", //$NON-NLS-1$ "${" + pathVariable + ".pathvariable}/" + pathVariableExtension); //$NON-NLS-1$ //$NON-NLS-2$ } else { String relativePath = ExportUtil.getRelativePath(file.getLocation() + IAntCoreConstants.EMPTY_STRING, projectRoot); variable2valueMap.put(srcDir + ".link", relativePath); //$NON-NLS-1$ } srcDir = "${" + srcDir + ".link}"; //$NON-NLS-1$ //$NON-NLS-2$ } return srcDir; } private void handleJars(IClasspathEntry entry) { if (entry.getContentKind() == IPackageFragmentRoot.K_BINARY && entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { String jarFile = entry.getPath().toString(); StringBuffer jarFileBuffer = new StringBuffer(); StringBuffer jarFileAbsoluteBuffer = new StringBuffer(); String jarFileAbsolute = ExportUtil.resolve(entry.getPath()); if (jarFileAbsolute == null) { jarFileAbsolute = jarFile; // jarFile was already absolute if (handleSubProjectClassesDirectory(jarFile, jarFileBuffer, jarFileAbsoluteBuffer)) { jarFile = jarFileBuffer.toString(); jarFileAbsolute = jarFileAbsoluteBuffer.toString(); } } String jarFileOld = jarFile; jarFile = ExportUtil.removeProjectRoot(jarFile, project.getProject()); if (jarFile.equals(jarFileOld)) { if (handleSubProjectClassesDirectory(jarFile, jarFileBuffer, jarFileAbsoluteBuffer)) { jarFile = jarFileBuffer.toString(); jarFileAbsolute = jarFileAbsoluteBuffer.toString(); } } rawClassPathEntries.add(jarFile); rawClassPathEntriesAbsolute.add(jarFileAbsolute); } } /** * Checks if file is a class directory of a subproject and fills string buffers with resolved values. * * @param file * file to check * @param jarFile * filled with file location with variable reference ${project.location}, which is also added to variable2valueMap * @param jarFileAbsolute * filled with absolute file location * @return true if file is a classes directory */ private boolean handleSubProjectClassesDirectory(String file, StringBuffer jarFile, StringBuffer jarFileAbsolute) { // class directory of a subproject? if (file != null && file.indexOf('/') == 0) { int i = file.indexOf('/', 1); i = (i != -1) ? i : file.length(); String subproject = file.substring(1, i); IJavaProject javaproject = ExportUtil.getJavaProjectByName(subproject); if (javaproject != null) { jarFile.setLength(0); jarFileAbsolute.setLength(0); String location = javaproject.getProject().getName() + ".location"; //$NON-NLS-1$ jarFileAbsolute.append(ExportUtil.replaceProjectRoot(file, javaproject.getProject(), ExportUtil.getProjectRoot(javaproject))); jarFile.append(ExportUtil.replaceProjectRoot(file, javaproject.getProject(), "${" + location + "}")); //$NON-NLS-1$ //$NON-NLS-2$ String projectRoot = ExportUtil.getProjectRoot(project); String relativePath = ExportUtil.getRelativePath(ExportUtil.getProjectRoot(javaproject), projectRoot); variable2valueMap.put(location, relativePath); return true; } } return false; } private void handleVariables(IClasspathEntry entry) { if (entry.getContentKind() == IPackageFragmentRoot.K_SOURCE && entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { // found variable String e = entry.getPath().toString(); int index = e.indexOf('/'); if (index == -1) { index = e.indexOf('\\'); } String variable = e; String path = IAntCoreConstants.EMPTY_STRING; if (index != -1) { variable = e.substring(0, index); path = e.substring(index); } IPath value = JavaCore.getClasspathVariable(variable); if (value != null) { String projectRoot = ExportUtil.getProjectRoot(project); String relativePath = ExportUtil.getRelativePath(value.toString(), projectRoot); variable2valueMap.put(variable, relativePath); } else if (variable2valueMap.get(variable) == null) { // only add empty value, if variable is new variable2valueMap.put(variable, IAntCoreConstants.EMPTY_STRING); } rawClassPathEntriesAbsolute.add(value + path); rawClassPathEntries.add("${" + variable + "}" + path); //$NON-NLS-1$ //$NON-NLS-2$ } } private void handleLibraries(IClasspathEntry entry) throws JavaModelException { if (entry.getContentKind() == IPackageFragmentRoot.K_SOURCE && entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { // found library IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project); if (container == null) { // jar missing (project not compile clean) return; } String jar = entry.getPath().toString(); String refName; if (jar.startsWith(JavaRuntime.JRE_CONTAINER)) { // JRE System Library refName = "${jre.container}"; //$NON-NLS-1$ } else if (jar.startsWith(JavaCore.USER_LIBRARY_CONTAINER_ID)) { // User Library String libraryName = container.getDescription(); refName = "${" + libraryName + ".userclasspath}"; //$NON-NLS-1$ //$NON-NLS-2$ if (container.getKind() == IClasspathContainer.K_SYSTEM) { refName = "${" + libraryName + ".bootclasspath}"; //$NON-NLS-1$ //$NON-NLS-2$ } } else { // Library dependencies: e.g. Plug-in Dependencies String libraryName = container.getDescription(); refName = "${" + libraryName + ".libraryclasspath}"; //$NON-NLS-1$ //$NON-NLS-2$ } userLibraryCache.put(refName, container); srcDirs.add(refName); classDirs.add(refName); rawClassPathEntries.add(refName); rawClassPathEntriesAbsolute.add(refName); inclusionLists.add(new ArrayList<String>()); exclusionLists.add(new ArrayList<String>()); } } private void handleProjects(IClasspathEntry entry) { if (entry.getContentKind() == IPackageFragmentRoot.K_SOURCE && entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { // found required project on build path String subProjectRoot = entry.getPath().toString(); IJavaProject subProject = ExportUtil.getJavaProject(subProjectRoot); if (subProject == null) { // project was not loaded in workspace AntUIPlugin.log("project is not loaded in workspace: " + subProjectRoot, null); //$NON-NLS-1$ return; } // only add an indicator that this is a project reference String classpathRef = "${" + subProject.getProject().getName() + ".classpath}"; //$NON-NLS-1$ //$NON-NLS-2$ srcDirs.add(classpathRef); classDirs.add(classpathRef); rawClassPathEntries.add(classpathRef); rawClassPathEntriesAbsolute.add(classpathRef); inclusionLists.add(new ArrayList<String>()); exclusionLists.add(new ArrayList<String>()); } } /** * Get runtime classpath items for given project separated with path separator. */ public static String getClasspath(IJavaProject project) throws CoreException { List<String> items = getClasspathList(project); return ExportUtil.toString(items, File.pathSeparator); } /** * Get runtime classpath items for given project. */ public static List<String> getClasspathList(IJavaProject project) throws CoreException { String[] classpath = JavaRuntime.computeDefaultRuntimeClassPath(project); return Arrays.asList(classpath); } /** * Check if given string is a reference. */ public static boolean isReference(String s) { return isProjectReference(s) || isUserLibraryReference(s) || isUserSystemLibraryReference(s) || isLibraryReference(s) || isJreReference(s); // NOTE: A linked resource is no reference } /** * Check if given string is a project reference. */ public static boolean isProjectReference(String s) { return s.startsWith("${") && s.endsWith(".classpath}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Resolves given project reference to a project. * * @return <code>null</code> if project is not resolvable */ public static IJavaProject resolveProjectReference(String s) { String name = ExportUtil.removePrefixAndSuffix(s, "${", ".classpath}"); //$NON-NLS-1$ //$NON-NLS-2$ return ExportUtil.getJavaProjectByName(name); } /** * Check if given string is a user library reference. */ public static boolean isUserLibraryReference(String s) { return s.startsWith("${") && s.endsWith(".userclasspath}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Check if given string is a user system library reference. This library is added to the compiler boot classpath. */ public static boolean isUserSystemLibraryReference(String s) { return s.startsWith("${") && s.endsWith(".bootclasspath}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Check if given string is a library reference. e.g. Plug-in dependencies are library references. * */ public static boolean isLibraryReference(String s) { return s.startsWith("${") && s.endsWith(".libraryclasspath}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Check if given string is a JRE reference. */ public static boolean isJreReference(String s) { return s.equals("${jre.container}"); //$NON-NLS-1$ } /** * Resolves given user (system) library or plugin reference to its container. * * <p> * NOTE: The library can only be resolved if an EclipseClasspath object was created which had a reference to this library. The class holds an * internal cache to circumvent that UserLibraryManager is an internal class. * * @return null if library is not resolvable */ public static IClasspathContainer resolveUserLibraryReference(String s) { return userLibraryCache.get(s); } /** * Check if given string is a linked resource. * */ public static boolean isLinkedResource(String s) { return s.startsWith("${") && s.endsWith(".link}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Get source folder name of a linked resource. * * @see #isLinkedResource(String) */ public static String getLinkedResourceName(String s) { return ExportUtil.removePrefixAndSuffix(s, "${", ".link}"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Resolves given linked resource to an absolute file location. */ public String resolveLinkedResource(String s) { String name = ExportUtil.removePrefixAndSuffix(s, "${", "}"); //$NON-NLS-1$ //$NON-NLS-2$ String value = variable2valueMap.get(name); String suffix = ".pathvariable}"; //$NON-NLS-1$ int i = value.indexOf(suffix); if (i != -1) { // path variable String pathVariable = value.substring(0, i + suffix.length() - 1); pathVariable = ExportUtil.removePrefix(pathVariable, "${"); //$NON-NLS-1$ return variable2valueMap.get(pathVariable) + value.substring(i + suffix.length()); } return value; } /** * Convert a VariableClasspathEntry to a IClasspathEntry. * * <p> * This is a workaround as entry.getClasspathEntry() returns null. */ private IClasspathEntry convertVariableClasspathEntry(IRuntimeClasspathEntry entry) { try { Document doc = ExportUtil.parseXmlString(entry.getMemento()); Element element = (Element) doc.getElementsByTagName("memento").item(0); //$NON-NLS-1$ String variableString = element.getAttribute("variableString"); //$NON-NLS-1$ ExportUtil.addVariable(variable2valueMap, variableString, ExportUtil.getProjectRoot(project)); // remove ${...} from string to be conform for handleVariables() variableString = ExportUtil.removePrefix(variableString, "${");//$NON-NLS-1$ int i = variableString.indexOf('}'); if (i != -1) { variableString = variableString.substring(0, i) + variableString.substring(i + 1); } IPath path = new Path(variableString); return JavaCore.newVariableEntry(path, null, null); } catch (Exception e) { AntUIPlugin.log(e); return null; } } }